package com.googlecode.connectlet.machine;

import com.googlecode.connectlet.packet.PacketException;
import com.googlecode.connectlet.packet.PacketParser;
import com.googlecode.connectlet.util.Bytes;

public class MachinePacket {
	public static final int HEAD_SIZE = 16;
	public static final int HEAD_TAG = 8341;

	private static PacketParser parser = new PacketParser() {
		@Override
		public int getPacketSize(byte[] b, int off, int len) throws PacketException {
			if (len < HEAD_SIZE) {
				return 0;
			}
			if (Bytes.toShort(b, off) != HEAD_TAG) {
				throw new PacketException("Wrong Packet Tag");
			}
			int size = Bytes.toShort(b, off + 2);
			if (size < HEAD_SIZE ||
					size != HEAD_SIZE + Bytes.toShort(b, off + 12) +
					Bytes.toShort(b, off + 14)) {
				throw new PacketException("Wrong Packet Size");
			}
			return size;
		}
	};

	public static PacketParser getParser() {
		return parser;
	}

	/**
	 * Tag for Test<br>
	 * Request = Header<br>
	 * Response = Header
	 */
	public static final int TAG_PING = 0;

	/**
	 * Tag for Allocating a new Machine Key<br>
	 * Request = Header<br>
	 * Response = Header
	 */
	public static final int TAG_KEY_NEW = 1;

	/**
	 * Tag for Putting a Machine Key into Machine<br>
	 * Request = Header + Machine-Key<br>
	 * Response = Header
	 */
	public static final int TAG_KEY_SET = 2;

	/**
	 * Tag for Dropping a Machine Key<br>
	 * Request = Header<br>
	 * Response = Header
	 */
	public static final int TAG_KEY_DEL = 3;

	/**
	 * Tag for Encrypting a Private-Key or Secret-Key by a Machine-Key<br>
	 * Request = Header + Private/Secret-Key<br>
	 * Response = Header + ECB-Sealed-Private/Secret-Key
	 */
	public static final int TAG_KEY_WRAP = 4;

	/**
	 * Tag for Generating a Key-Pair and Encrypting the Private-Key by a Machine-Key<br>
	 * Request = Header<br>
	 * Response = Header + Public-Key + ECB-Sealed-Private-Key
	 */
	public static final int TAG_KEYPAIR_NEW = 5;

	/**
	 * Tag for Decrypting or Signing Data by a Machine-Key-Sealed Private-Key<br>
	 * Request = Header + ECB-Sealed-Private-Key + Input-Data<br>
	 * Response = Header + Output-Data
	 */
	public static final int TAG_PRIVATE_RSADP = 6;

	/**
	 * Tag for Encryping Data by a Machine-Key-Sealed Secret-Key<br>
	 * Request = Header + Secret-Key + Plain-Data<br>
	 * Response = Header + IV-With-CBC-Sealed-Data
	 */
	public static final int TAG_SECRET_ENC_CBC = 7;

	/**
	 * Tag for Decryping Data by a Machine-Key-Sealed Secret-Key<br>
	 * Request = Header + Secret-Key + IV-With-CBC-Sealed-Data<br>
	 * Response = Header + Plain-Data
	 */
	public static final int TAG_SECRET_DEC_CBC = 8;

	public static final int TAG_OK = 0;
	public static final int TAG_ERROR = 99;

	private int requestTag, keyId, keySize;
	private byte[] key, data;

	public MachinePacket(int requestTag) {
		this(requestTag, 0, 0, Bytes.EMPTY_BYTES, Bytes.EMPTY_BYTES);
	}

	public MachinePacket(int requestTag, int keyId) {
		this(requestTag, keyId, 0, Bytes.EMPTY_BYTES, Bytes.EMPTY_BYTES);
	}

	public MachinePacket(int requestTag, int keyId, int keySize) {
		this(requestTag, keyId, keySize, Bytes.EMPTY_BYTES, Bytes.EMPTY_BYTES);
	}

	public MachinePacket(int requestTag, int keyId, int keySize, byte[] key) {
		this(requestTag, keyId, keySize, key, Bytes.EMPTY_BYTES);
	}

	public MachinePacket(int requestTag, int keyId, int keySize, byte[] key, byte[] data) {
		this.requestTag = requestTag;
		this.keySize = keySize;
		this.keyId = keyId;
		this.key = key;
		this.data = data;
	}

	public MachinePacket(byte[] b, int off) {
		requestTag = Bytes.toShort(b, off + 4);
		keySize = Bytes.toShort(b, off + 6);
		keyId = Bytes.toInt(b, off + 8);
		key = Bytes.sub(b, off + 16, Bytes.toShort(b, off + 12));
		data = Bytes.sub(b, off + 16 + key.length, Bytes.toShort(b, off + 14));
	}

	public byte[] getEncoded() {
		byte[] head = new byte[16];
		Bytes.setShort(HEAD_TAG, head, 0);
		Bytes.setShort(16 + key.length + data.length, head, 2);
		Bytes.setShort(requestTag, head, 4);
		Bytes.setShort(keySize, head, 6);
		Bytes.setInt(keyId, head, 8);
		Bytes.setShort(key.length, head, 12);
		Bytes.setShort(data.length, head, 14);
		return Bytes.add(head, key, data);
	}

	public int getRequestTag() {
		return requestTag;
	}

	public int getKeySize() {
		return keySize;
	}

	public int getKeyId() {
		return keyId;
	}

	public byte[] getKey() {
		return key;
	}

	public byte[] getData() {
		return data;
	}
}